#include "../../core/pch.h"
#include "../../pi/binarysemaphore.h"
#include "../../pi/process.h"

unsigned int findCoordinatorIp(int ipg_port)
{
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == SOCKET_ERROR)
	{
		DEBUGC("find: error while creating socket (%s)", strerror(errno));
		exit(SOCKET_ERROR);
	}

	int broadcast = 1;
	int status = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
	if (status == SETSOCKOPT_ERROR)
	{
		DEBUGC("find: error while setting socket option (%s)", strerror(errno));
		exit(SETSOCKOPT_ERROR);
	}

	struct sockaddr_in bcast_addr;
	FILL_SOCKADDR_IN_INT(bcast_addr, AF_INET, ipg_port, INADDR_BROADCAST);
	char msg = ARE_YOU_NUMFAC;
	unsigned int packedIp = 0;
	while (true)
	{
		sendto(sock, &msg, sizeof(char), 0, (struct sockaddr*) &bcast_addr, sizeof(bcast_addr));
		struct timeval tv;
		fd_set readfds;

		tv.tv_sec = 2;
		tv.tv_usec = 0;

		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);

		DEBUGC("slave: looking for master computer");

		int ret = select(sock+1, &readfds, NULL, NULL, &tv);
		if (ret == SELECT_ERROR)
		{
			DEBUGC("find: error on select (%s)", strerror(errno));
			exit(SELECT_ERROR);
		}

		socklen_t len = sizeof(bcast_addr);
		if (FD_ISSET(sock, &readfds))
		{
			recvfrom(sock, &msg, sizeof(char), 0, (struct sockaddr*) &bcast_addr, &len);
			if (msg == I_AM_NUMFAC)
			{
				recvfrom(sock, &packedIp, sizeof(int), 0, (struct sockaddr*) &bcast_addr, &len);
				return packedIp;
			}
		}
		else
		{
			DEBUGS("slave: timed out while proobing for coordinator");
		}
	}
}

char* unpackIp(unsigned int ip)
{
	char* buf = (char*) malloc(sizeof(char) * 16);
	char* ptr = buf;
	memset(buf, 0, 16);

	sprintf(ptr, "%d", (ip & 0xff000000) >> 24);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x00ff0000) >> 16);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x0000ff00) >> 8);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x000000ff));

	return buf;
}


/** Variable for statuses for sockets functions. */
int status;

int worker_port;
int logger_port;
int receiver_port;
int ipg_port;

int log_sock;

/**
 * Semaphore for mutual exclusive access to 
 * log_sock shared by slave and worker.
 */
int sem_id;

char* coordIp;

/** For algorithm purposes. */
unsigned long long N, M, p;
unsigned int num;

int worker()
{
	DEBUGC("worker: started working");

	struct sockaddr_in receiver_addr, worker_addr;
	FILL_SOCKADDR_IN_TXT(receiver_addr, AF_INET, receiver_port, coordIp);
	FILL_SOCKADDR_IN_INT(worker_addr, AF_INET, worker_port, INADDR_ANY);

	/** Create listening socket for worker. */
	int worker_sock = socket(AF_INET, SOCK_STREAM, 0); 
	if (worker_sock == SOCKET_ERROR)
	{
		DEBUGC("worker: error while creating listening socket for worker (%s)", strerror(errno));
		exit(SOCKET_ERROR);
	}

	/** Reuse addresses. */
	int yes = 1;
	setsockopt(worker_sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	status = bind(worker_sock, (struct sockaddr*) &worker_addr, sizeof(worker_addr)); 
	if (status == BIND_ERROR)
	{
		DEBUGC("worker: error while binding socket to address (%s)", strerror(errno));
		exit(BIND_ERROR);
	}

	status = listen(worker_sock, 64); 
	if (status == LISTEN_ERROR)
	{
		DEBUGC("worker: error while trying to listen on socket (%s)", strerror(errno));
		exit(LISTEN_ERROR);
	}

	struct sockaddr_in cliaddr;
	socklen_t len = sizeof(cliaddr);

	char msg;
	while (true)
	{
		DEBUGC("worker: waiting for request");

		int sender_sock = accept(worker_sock, (struct sockaddr*) &cliaddr, &len);
		if (sender_sock == ACCEPT_ERROR)
		{
			DEBUGC("worker: error while accepting connection (%s)", strerror(errno));
			exit(ACCEPT_ERROR);
		}

		DEBUGC("worker: accepted request");

		/** Receive type of work request. */
		recv(sender_sock, &msg, sizeof(char), 0);

		int times_count;
		int receiver_sock;
		switch (msg)
		{
			case FACTOR_REQUEST:
				/** Receiver data for algorithm. */
				recv(sender_sock, &N, sizeof(unsigned long long), 0);
				recv(sender_sock, &p, sizeof(unsigned long long), 0);
				recv(sender_sock, &num, sizeof(unsigned int), 0);

				close(sender_sock);

				/** N equals one means nothing to do. */
				if (N == 1) exit(0);

				DEBUGC("worker: factor request of %llu with prime %llu", N, p);

				/** Start the algorithm. */
				times_count = 0;
				M = N;
				while ((N % p) == 0 && p > 1)
				{
					N = N / p;
					times_count++;
				}

				/** Send back the results. */
				receiver_sock = socket(AF_INET, SOCK_STREAM, 0);
				if (receiver_sock == SOCKET_ERROR)
				{
					DEBUGC("worker: error while creating socket for receiver to send back results (%s)",
						strerror(errno));
					exit(SOCKET_ERROR);
				}

				status = connect(receiver_sock, (struct sockaddr*) &receiver_addr, sizeof(receiver_addr));
				if (status == CONNECT_ERROR)
				{
					DEBUGC("worker: error while connecting to receiver (%s)", strerror(errno));
					exit(CONNECT_ERROR);
				}

				send(receiver_sock, &M, sizeof(unsigned long long), 0);
				send(receiver_sock, &p, sizeof(unsigned long long), 0);
				send(receiver_sock, &times_count, sizeof(int), 0);
				send(receiver_sock, &num, sizeof(unsigned int), 0);
				close(receiver_sock);

				/** Signalise idleness. */
				msg = IM_IDLE;
				BinarySemaphore::Decrement(sem_id);
				send(log_sock, &msg, sizeof(char), 0);
				BinarySemaphore::Increment(sem_id);

				DEBUGC("im idle signaling ended");

				break;
			case END_OF_COMPUTATION:
				kill(getppid(), SIGKILL);
				DEBUGC("worker: exiting");
				exit(STATUS_OK);
			default:
				DEBUGC("worker: not recognized protocol message (%d), exitinig", msg);
				exit(STATUS_ERR);
				break;
		}
	}

	return 0;
}

/** Options for slave process. */
struct option long_options[] =
{ 
	{"workport", required_argument, 0, 'a'},
	{"logport", required_argument, 0, 'b'},
	{"recvport", required_argument, 0, 'c'},
	{"ipgport", required_argument, 0, 'd'},
	{0, 0, 0, 0}
};

int main(int argc, char* argv[])
{
	if (argc < 9)
	{
		DEBUGC("Usage: slave --workport <port> --logport <port> --recvport <port> --ipgport <port>");
		exit(STATUS_ERR);
	}

	while (true)
	{
		int option_index = 0;
		int op = getopt_long(argc, argv, "a:b:c:d:", long_options, &option_index);
		if (op == NO_MORE_OPTIONS)
			break;

		switch (op)
		{
			case 'a':
				worker_port = atoi(optarg);
				break;
			case 'b':
				logger_port = atoi(optarg);
				break;
			case 'c':
				receiver_port = atoi(optarg);
				break;
			case 'd':
				ipg_port = atoi(optarg);
				break;
			default:
				exit(STATUS_ERR);
		}
	}

	/** Let's connect to logger. */
	unsigned int packedIp = findCoordinatorIp(ipg_port);
	coordIp = unpackIp(packedIp);

	struct sockaddr_in logger_addr;
	FILL_SOCKADDR_IN_TXT(logger_addr, AF_INET, logger_port, unpackIp(packedIp));

	log_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (log_sock == SOCKET_ERROR)
	{
		DEBUGC("slave: error while creating socket for logger (%s)", strerror(errno));
		exit(SOCKET_ERROR);
	}

	status = connect(log_sock, (struct sockaddr*) &logger_addr, sizeof(logger_addr));
	if (status == CONNECT_ERROR)
	{
		DEBUGC("slave: error while connecting to logger (%s)", strerror(errno));
		exit(CONNECT_ERROR);
	}
	DEBUGC("slave: connected to logger, sending my pid to logger");

	/** Create and initialiaze semaphore for log_sock. */
	BinarySemaphore::Create(sem_id);
	BinarySemaphore::Init(sem_id);

	/** Start worker process. */
	int worker_pid;
	if ((worker_pid = Process::Create()) == 0)
	{
		worker();
		exit(STATUS_OK);
	}
	
	/** Send pid to logger. */
	pid_t my_pid = getpid();
	send(log_sock, &my_pid, sizeof(pid_t), 0);
	DEBUGC("slave: sent pid to logger");

	/** Send to logger worker port. */
	send(log_sock, &worker_port, sizeof(int), 0);
	DEBUGC("slave: sent worker port to logger");

//#ifdef FEAT_IM_ALIVE_SIGNALING
	char msg;
	while (true)
	{
		msg = ALIVE_MSG;
		BinarySemaphore::Decrement(sem_id);
		if (send(log_sock, &msg, sizeof(char), 0) < sizeof(char)) 
		{
			DEBUGC("slave: exiting");
			exit(STATUS_OK);
		}
		BinarySemaphore::Increment(sem_id);
		//DEBUGC("slave: sent ALIVE_MSG, sleeping for %d s", TIME_FOR_IM_ALIVE_NEXT_MSG);

		sleep(TIME_FOR_IM_ALIVE_NEXT_MSG);
	}
//#endif

	int status;
	wait(&status);

	close(log_sock);

	return 0;
}

